package cz.cuni.amis.pogamut.episodic.decisions;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;

/**
 * Class <code>Action</code> represents an action node from decision process
 * in pogamut end of the project. It is part of decision tree and represent
 * universal OR node in the decision AND-OR trees. In order to perform
 * an action, all its sub-goals must be fullfilled and an action can fulfill
 * one given super-goal. It is extending the <code>Node</code> class, so it is
 * possible to attach specified affordances to the <code>Action</code> node.
 * <p>
 * By pointing to the trace of <code>Intention</code>s and <code>Action</code>s
 * it is possible to add new node to the episode tree. It is the only way how
 * to add new information to memory.
 * <p>
 * <code>AtomicAction</code> nodes can be attached on to <code>Action</code>
 * nodes. They cannot be attached to the <code>Intention</code> nodes.
 *
 * @author Michal Cermak
 * @see Node
 */
public class Action extends Node implements Serializable {
    /**
     * Determines if a de-serialized file is compatible with this class.
     *
     * Maintainers must change this value if and only if the new version
     * of this class is not compatible with old versions. See Sun docs
     * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
     * /serialization/spec/version.doc.html> details. </a>
     *
     * Not necessary to include in first version of the class, but
     * included here as a reminder of its importance.
     */
    private static final long serialVersionUID = 1L;

    int possibleSubTrees = 0;

    /**
     * Each <code>Action</code> node can have unlimited number of <code>Intention</code>
     * subnodes. The only restriction is that no two subnodes can have identical
     * name. These subnodes are goals that all need to be completed in order
     * to perform this action - they are AND nodes.
     */
    private HashMap<String, Intention> subNodes = new HashMap<String, Intention>();

    /**
     * Each <code>Action</code> node can have unlimited number of
     * <code>AtomicAction</code> subnodes. The only restriction is that no two
     * subnodes can have identical name. These subnodes are atomic actions
     * that will be executed once all goals are satisfied and agent is performing
     * this action. It is reasonable that action nodes without any subgoals
     * will have at least one atomic action attached to it.
     */
    private HashMap<String, AtomicAction> atomicActions = new HashMap<String, AtomicAction>();

    /**
     * Instantiate the class by providing its name and attractivity.
     * <p>
     * There can be several nodes with identical name in the decision tree,
     * but no node can have two direct children with the same name.
     *
     * @param   name    Name of the new node.
     * @param   _attractivity   Specified in XML file. Helps determine when to forget node.
     */
    public Action(String name, int attractivity) {
        super(name, attractivity, NodeType.ACTION);
    }

    /**
     * Attach new atomic action that will be executed when performing this
     * action node. Each <code>Action</code> node can have unlimited number of
     * <code>AtomicAction</code> subnodes. The only restriction is that no two
     * subnodes can have identical name. These subnodes are atomic actions
     * that will be executed once all goals are satisfied and agent is performing
     * this action. It is reasonable that action nodes without any subgoals
     * will have at least one atomic action attached to it.
     * <p>
     * No attached atomic action node can share identical name with any
     * direct goal subnode.
     *
     * @param   aa    Reference to an already instanciated <code>AtomicAction</code>
     * class that should be added attached to this action node.
     */
    public void addAtomicAction(AtomicAction aa) {
        atomicActions.put(aa.getName(), aa);
        aa.parent = this;
    }

    /**
     * Basically a getter method for the <code>atomicActions</code> variable.
     *
     * @return Returns a collection of a all <code>AtomicAction</code>s
     * attached to this <code>Action</code> node. These are atomic actions
     * that will be executed once all goals are satisfied and agent is performing
     * this action.
     */
    public Collection<AtomicAction> getAtomicActions() {
        return atomicActions.values();
    }

    /**
     * This method returns one specified <code>AtomicAction</code> node that
     * is attached to this node.
     *
     * @param   s   Name of the atomic action that is to be returned.
     * @return Returns an <code>AtomicAction</code> node with specified
     * name attached to this <code>Action</code> node. Returns <code>null</code>
     * if there is no such <code>AtomicAction</code> attached to it.
     */
    public AtomicAction getAtomicAction(String s) {
        return atomicActions.get(s);
    }

    /**
     * Adds a new goal subnode that has to be satisfied first in order to perform
     * the action representing by the current node. Each <code>Action</code>
     * node can have unlimited number of <code>Intention</code> subnodes.
     * The only restriction is that no two direct subnodes can have identical
     * name. These subnodes are goals that all need to be completed in order
     * to perform this action - they are AND nodes.
     * <p>
     * This method is used to create the decision tree structure during memory
     * initialization.
     * <p>
     * No direct goal subnode can share identical name with attached atomic
     * action node.
     *
     * @param   intention   Reference to an already instanciated <code>Intention</code>
     * class that should be added attached to this <code>Action</code> node.
     */
    public void addSubNode(Intention intention) {
        subNodes.put(intention.getName(), intention);
        intention.parent = this;
    }

    /**
     * Basically a getter method for the <code>subNodes</code> variable.
     * These subnodes are goals that all need to be completed in order
     * to perform this action - they are AND nodes. Together with attached
     * atomic actions and affordances they are direct children of this node
     * in the decision tree.
     *
     * @return Returns a collection of a all <code>Intetion</code> (goal)
     * subnodes of this <code>Action</code> node.
     */
    public Collection<Intention> getSubNodes() {
        return subNodes.values();
    }

    /**
     * This method returns one specified <code>Intention</code> node that
     * is direct subnode of this node. If no such node exists it tries to
     * find and atomic action of such name.
     *
     * @param   name    Name of the node that is to be returned.
     * @return Returns an <code>Intention</code> node with specified
     * name that is direct subnode of this <code>Action</code> node.
     * If no such goal exists returns <code>AtomicAction</code> node
     * attached to this node. Returns <code>null</code> if there is
     * no such <code>AtomicAction</code> attached to it.
     */
    public Node getSubNode(String name) {
        Node node = subNodes.get(name);
        //because this will be used to get a reference to associated node
        // and atomic actions are linked with associated nodes too.
        if (node == null) node = atomicActions.get(name);
        return node;
    }

    /**
     * This method returns one specified <code>Intention</code> node that
     * is direct subnode of this node.
     *
     * @param   name    Name of the node that is to be returned.
     * @return Returns an <code>Intention</code> node with specified
     * name that is direct subnode of this <code>Action</code> node.
     * Returns <code>null</code> if there is
     * no such <code>Intention</code> attached to it.
     */
    public Intention getSubIntention (String name) {
        return subNodes.get(name);
    }

    /**
     * This method returns <code>Intention</code> nodes that
     * are direct subnodes of this node and atomic action nodes.
     *
     * @return Returns a collection of <code>Intention</code> nodes that
     * are direct subnodes of this <code>Action</code> node and
     * <code>AtomicAction</code> nodes attached to this node.
     */
    public Collection<Node> getAllChildrenNodes() {
        Collection<Node> col = new HashSet<Node>();
        col.addAll(subNodes.values());
        col.addAll(atomicActions.values());
        return col;
    }

    public int getPossibleSubTrees() {
        if (possibleSubTrees == 0) {
            possibleSubTrees = 1;
            for (Intention i : getSubNodes()) {
                possibleSubTrees *= i.getPossibleSubTrees();
            }
        }
        return possibleSubTrees;
    }
}
